home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / ANSI / c-client / mh.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-30  |  35.6 KB  |  1,255 lines

  1. /*
  2.  * Program:    MH mail routines
  3.  *
  4.  * Author(s):    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  *        Andrew Cohen
  13.  *        Internet: cohen@bucrf16.bu.edu
  14.  *
  15.  * Date:    23 February 1992
  16.  * Last Edited:    30 June 1993
  17.  *
  18.  * Copyright 1993 by the University of Washington
  19.  *
  20.  *  Permission to use, copy, modify, and distribute this software and its
  21.  * documentation for any purpose and without fee is hereby granted, provided
  22.  * that the above copyright notice appears in all copies and that both the
  23.  * above copyright notice and this permission notice appear in supporting
  24.  * documentation, and that the name of the University of Washington not be
  25.  * used in advertising or publicity pertaining to distribution of the software
  26.  * without specific, written prior permission.  This software is made
  27.  * available "as is", and
  28.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  29.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  30.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  31.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  32.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  33.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  34.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  35.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  36.  *
  37.  */
  38.  
  39. #include <stdio.h>
  40. #include <ctype.h>
  41. #include <netdb.h>
  42. #include <errno.h>
  43. extern int errno;        /* just in case */
  44. #include <sys/types.h>
  45. #include "mail.h"
  46. #include "osdep.h"
  47. #include <pwd.h>
  48. #include <sys/file.h>
  49. #include <sys/stat.h>
  50. #include <sys/time.h>
  51. #include "mh.h"
  52. #include "rfc822.h"
  53. #include "misc.h"
  54.  
  55. /* MH mail routines */
  56.  
  57.  
  58. /* Driver dispatch used by MAIL */
  59.  
  60. DRIVER mhdriver = {
  61.   "mh",                /* driver name */
  62.   (DRIVER *) NIL,        /* next driver */
  63.   mh_valid,            /* mailbox is valid for us */
  64.   mh_parameters,        /* manipulate parameters */
  65.   mh_find,            /* find mailboxes */
  66.   mh_find_bboards,        /* find bboards */
  67.   mh_find_all,            /* find all mailboxes */
  68.   mh_find_all_bboards,        /* find all bboards */
  69.   mh_subscribe,            /* subscribe to mailbox */
  70.   mh_unsubscribe,        /* unsubscribe from mailbox */
  71.   mh_subscribe_bboard,        /* subscribe to bboard */
  72.   mh_unsubscribe_bboard,    /* unsubscribe from bboard */
  73.   mh_create,            /* create mailbox */
  74.   mh_delete,            /* delete mailbox */
  75.   mh_rename,            /* rename mailbox */
  76.   mh_open,            /* open mailbox */
  77.   mh_close,            /* close mailbox */
  78.   mh_fetchfast,            /* fetch message "fast" attributes */
  79.   mh_fetchflags,        /* fetch message flags */
  80.   mh_fetchstructure,        /* fetch message envelopes */
  81.   mh_fetchheader,        /* fetch message header only */
  82.   mh_fetchtext,            /* fetch message body only */
  83.   mh_fetchbody,            /* fetch message body section */
  84.   mh_setflag,            /* set message flag */
  85.   mh_clearflag,            /* clear message flag */
  86.   mh_search,            /* search for message based on criteria */
  87.   mh_ping,            /* ping mailbox to see if still alive */
  88.   mh_check,            /* check for new messages */
  89.   mh_expunge,            /* expunge deleted messages */
  90.   mh_copy,            /* copy messages to another mailbox */
  91.   mh_move,            /* move messages to another mailbox */
  92.   mh_append,            /* append string message to mailbox */
  93.   mh_gc                /* garbage collect stream */
  94. };
  95.  
  96.                 /* prototype stream */
  97. MAILSTREAM mhproto = {&mhdriver};
  98.  
  99. /* MH mail validate mailbox
  100.  * Accepts: mailbox name
  101.  * Returns: our driver if name is valid, NIL otherwise
  102.  */
  103.  
  104. DRIVER *mh_valid (char *name)
  105. {
  106.   char tmp[MAILTMPLEN];
  107.   return mh_isvalid (name,tmp) ? &mhdriver : NIL;
  108. }
  109.  
  110.  
  111. /* MH mail test for valid mailbox
  112.  * Accepts: mailbox name
  113.  * Returns: T if valid, NIL otherwise
  114.  */
  115.  
  116. int mh_isvalid (char *name,char *tmp)
  117. {
  118.   struct stat sbuf;
  119.                                 /* if file, get its status */
  120.   return (*name != '{' && *name != '.' &&
  121.       (stat (mh_file (tmp,name),&sbuf) == 0) &&
  122.       (sbuf.st_mode & S_IFMT) == S_IFDIR);
  123. }
  124.  
  125.  
  126. /* MH mail build file name
  127.  * Accepts: destination string
  128.  *          source
  129.  * Returns: destination
  130.  */
  131.  
  132. char *mh_file (char *dst,char *name)
  133. {
  134. #if 0
  135.   struct passwd *pw;
  136.   char *s,*t,tmp[MAILTMPLEN];
  137.   switch (*name) {
  138.   case '/':            /* absolute file path */
  139.     strcpy (dst,name);        /* copy the mailbox name */
  140.     break;
  141.   case '~':            /* home directory */
  142.     if (name[1] == '/') t = myhomedir ();
  143.     else {
  144.       strcpy (tmp,name + 1);    /* copy user name */
  145.       if (s = strchr (tmp,'/')) *s = '\0';
  146.       t = ((pw = getpwnam (tmp)) && pw->pw_dir) ? pw->pw_dir : "/NOSUCHUSER";
  147.     }
  148.     sprintf (dst,"%s%s",t,(s = strchr (name,'/')) ? s : "");
  149.     break;
  150.   default:            /* relative path, goes in Mail folder */
  151.     sprintf (dst,"%s/Mail/%s",myhomedir (),name);
  152.     break;
  153.   }
  154. #else
  155.   sprintf (dst,"%s/Mail/%s",myhomedir (),name);
  156. #endif
  157.   return dst;
  158. }
  159.  
  160.  
  161. /* MH manipulate driver parameters
  162.  * Accepts: function code
  163.  *        function-dependent value
  164.  * Returns: function-dependent return value
  165.  */
  166.  
  167. void *mh_parameters (long function,void *value)
  168. {
  169.   fatal ("Invalid mh_parameters function");
  170.   return NIL;
  171. }
  172.  
  173. /* MH mail find list of mailboxes
  174.  * Accepts: mail stream
  175.  *        pattern to search
  176.  */
  177.  
  178. void mh_find (MAILSTREAM *stream,char *pat)
  179. {
  180.   void *s = NIL;
  181.   char *t,tmp[MAILTMPLEN];
  182.   while (t = sm_read (&s))    /* read subscription database */
  183.     if ((*t != '{') && strcmp (t,"INBOX") && pmatch (t,pat) &&
  184.     mh_isvalid (t,tmp)) mm_mailbox (t);
  185. }
  186.  
  187.  
  188. /* MH mail find list of bboards
  189.  * Accepts: mail stream
  190.  *        pattern to search
  191.  */
  192.  
  193. void mh_find_bboards (MAILSTREAM *stream,char *pat)
  194. {
  195.   /* Always a no-op */
  196. }
  197.  
  198. /* MH mail find list of all mailboxes
  199.  * Accepts: mail stream
  200.  *        pattern to search
  201.  */
  202.  
  203. void mh_find_all (MAILSTREAM *stream,char *pat)
  204. {
  205.   DIR *dirp;
  206.   struct direct *d;
  207.   char tmp[MAILTMPLEN],file[MAILTMPLEN];
  208.   int i = 0;
  209.   char *s,*t;
  210.   if (s = strrchr (pat,'/')) {    /* directory specified in pattern? */
  211.     strncpy (file,pat,i = (++s) - pat);
  212.     file[i] = '\0';        /* tie off prefix */
  213.     t = mh_file (tmp,pat);    /* make fully-qualified file name */
  214.                 /* tie off directory name */
  215.     if (s = strrchr (t,'/')) *s = '\0';
  216.   }
  217.   else t = myhomedir ();    /* use home directory to search */
  218.   if (dirp = opendir (t)) {    /* now open that directory */
  219.     while (d = readdir (dirp)) {/* for each directory entry */
  220.       strcpy (file + i,d->d_name);
  221.       if (pmatch (file,pat) && (mh_isvalid (file,tmp))) mm_mailbox (file);
  222.     }
  223.     closedir (dirp);        /* flush directory */
  224.   }
  225. }
  226.  
  227.  
  228. /* MH mail find list of all bboards
  229.  * Accepts: mail stream
  230.  *        pattern to search
  231.  */
  232.  
  233. void mh_find_all_bboards (MAILSTREAM *stream,char *pat)
  234. {
  235.   /* Always a no-op */
  236. }
  237.  
  238. /* MH mail subscribe to mailbox
  239.  * Accepts: mail stream
  240.  *        mailbox to add to subscription list
  241.  * Returns: T on success, NIL on failure
  242.  */
  243.  
  244. long mh_subscribe (MAILSTREAM *stream,char *mailbox)
  245. {
  246.   char tmp[MAILTMPLEN];
  247.   return sm_subscribe (mh_file (tmp,mailbox));
  248. }
  249.  
  250.  
  251. /* MH mail unsubscribe to mailbox
  252.  * Accepts: mail stream
  253.  *        mailbox to delete from subscription list
  254.  * Returns: T on success, NIL on failure
  255.  */
  256.  
  257. long mh_unsubscribe (MAILSTREAM *stream,char *mailbox)
  258. {
  259.   char tmp[MAILTMPLEN];
  260.   return sm_unsubscribe (mh_file (tmp,mailbox));
  261. }
  262.  
  263.  
  264. /* MH mail subscribe to bboard
  265.  * Accepts: mail stream
  266.  *        bboard to add to subscription list
  267.  * Returns: T on success, NIL on failure
  268.  */
  269.  
  270. long mh_subscribe_bboard (MAILSTREAM *stream,char *mailbox)
  271. {
  272.   return NIL;            /* never valid for MH */
  273. }
  274.  
  275.  
  276. /* MH mail unsubscribe to bboard
  277.  * Accepts: mail stream
  278.  *        bboard to delete from subscription list
  279.  * Returns: T on success, NIL on failure
  280.  */
  281.  
  282. long mh_unsubscribe_bboard (MAILSTREAM *stream,char *mailbox)
  283. {
  284.   return NIL;            /* never valid for MH */
  285. }
  286.  
  287. /* MH mail create mailbox
  288.  * Accepts: mail stream
  289.  *        mailbox name to create
  290.  * Returns: T on success, NIL on failure
  291.  */
  292.  
  293. long mh_create (MAILSTREAM *stream,char *mailbox)
  294. {
  295.   return NIL;            /* this driver is read-only */
  296. }
  297.  
  298.  
  299. /* MH mail delete mailbox
  300.  *        mailbox name to delete
  301.  * Returns: T on success, NIL on failure
  302.  */
  303.  
  304. long mh_delete (MAILSTREAM *stream,char *mailbox)
  305. {
  306.   return NIL;            /* this driver is read-only */
  307. }
  308.  
  309.  
  310. /* MH mail rename mailbox
  311.  * Accepts: MH mail stream
  312.  *        old mailbox name
  313.  *        new mailbox name
  314.  * Returns: T on success, NIL on failure
  315.  */
  316.  
  317. long mh_rename (MAILSTREAM *stream,char *old,char *new)
  318. {
  319.   return NIL;            /* this driver is read-only */
  320. }
  321.  
  322. /* MH mail open
  323.  * Accepts: stream to open
  324.  * Returns: stream on success, NIL on failure
  325.  */
  326.  
  327. MAILSTREAM *mh_open (MAILSTREAM *stream)
  328. {
  329.   long i,nmsgs;
  330.   long recent = 0;
  331.   char tmp[MAILTMPLEN];
  332.   struct hostent *host_name;
  333.   struct direct **names;
  334.   if (!stream) return &mhproto;    /* return prototype for OP_PROTOTYPE call */
  335.   if (LOCAL) {            /* close old file if stream being recycled */
  336.     mh_close (stream);        /* dump and save the changes */
  337.     stream->dtb = &mhdriver;    /* reattach this driver */
  338.     mail_free_cache (stream);    /* clean up cache */
  339.   }
  340.   mh_file (tmp,stream->mailbox);/* canonicalize the stream mailbox name */
  341.   if (!strcmp (tmp,stream->mailbox)) {
  342.     fs_give ((void **) &stream->mailbox);
  343.     stream->mailbox = cpystr (tmp);
  344.   }
  345.   if (!lhostn) {        /* have local host yet? */
  346.     gethostname(tmp,MAILTMPLEN);/* get local host name */
  347.     lhostn = cpystr ((host_name = gethostbyname (tmp)) ?
  348.              host_name->h_name : tmp);
  349.   }
  350.                 /* scan directory */
  351.   if ((nmsgs = scandir (tmp,&names,mh_select,mh_numsort)) >= 0) {
  352.     stream->local = fs_get (sizeof (MHLOCAL));
  353.     LOCAL->dirty = NIL;        /* no update to .mhrc needed yet */
  354.     LOCAL->dir = cpystr (tmp);    /* copy directory name for later */
  355.                 /* create cache */
  356.     LOCAL->number = (unsigned long *) fs_get (nmsgs * sizeof (unsigned long));
  357.     LOCAL->header = (char **) fs_get (nmsgs * sizeof (char *));
  358.     LOCAL->body = (char **) fs_get (nmsgs * sizeof (char *));
  359.     LOCAL->seen = (char *) fs_get (nmsgs * sizeof (char));
  360.     for (i = 0; i<nmsgs; ++i) {    /* initialize cache */
  361.       LOCAL->number[i] = atoi (names[i]->d_name);
  362.       fs_give ((void **) &names[i]);
  363.       LOCAL->header[i] = LOCAL->body[i] = NIL;
  364.       LOCAL->seen[i] = NIL;
  365.     }
  366.     fs_give ((void **) &names);    /* free directory */
  367.                 /* make temporary buffer */
  368.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  369.     stream->sequence++;        /* bump sequence number */
  370.     stream->readonly = T;    /* make sure higher level knows readonly */
  371.     mail_exists (stream,nmsgs);    /* notify upper level that messages exist */
  372.  
  373.     while (i < nmsgs) {        /* mark all remaining messages as new */
  374.       LOCAL->seen[i++] = NIL;
  375.       mail_elt (stream,i)->recent = T;
  376.       ++recent;            /* count another recent message */
  377.     }
  378.     mail_recent (stream,recent);/* notify upper level about recent */
  379.                 /* notify if empty bboard */
  380.     if (!(stream->nmsgs || stream->silent)) mm_log ("folder is empty",WARN);
  381.   }
  382.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  383. }
  384.  
  385.  
  386. /* MH file name selection test
  387.  * Accepts: candidate directory entry
  388.  * Returns: T to use file name, NIL to skip it
  389.  */
  390.  
  391. int mh_select (struct direct *name)
  392. {
  393.   char c;
  394.   char *s = name->d_name;
  395.   while (c = *s++) if (!isdigit (c)) return NIL;
  396.   return T;
  397. }
  398.  
  399.  
  400. /* MH file name comparision
  401.  * Accepts: first candidate directory entry
  402.  *        second candidate directory entry
  403.  * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
  404.  */
  405.  
  406. int mh_numsort (struct direct **d1,struct direct **d2)
  407. {
  408.   return (atoi ((*d1)->d_name) - atoi ((*d2)->d_name));
  409. }
  410.  
  411. /* MH mail close
  412.  * Accepts: MAIL stream
  413.  */
  414.  
  415. void mh_close (MAILSTREAM *stream)
  416. {
  417.   if (LOCAL) {            /* only if a file is open */
  418.     mh_check (stream);        /* dump final checkpoint */
  419.     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
  420.     mh_gc (stream,GC_TEXTS);    /* free local cache */
  421.     fs_give ((void **) &LOCAL->number);
  422.     fs_give ((void **) &LOCAL->header);
  423.     fs_give ((void **) &LOCAL->body);
  424.     fs_give ((void **) &LOCAL->seen);
  425.                 /* free local scratch buffer */
  426.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  427.                 /* nuke the local data */
  428.     fs_give ((void **) &stream->local);
  429.     stream->dtb = NIL;        /* log out the DTB */
  430.   }
  431. }
  432.  
  433. /* MH mail fetch fast information
  434.  * Accepts: MAIL stream
  435.  *        sequence
  436.  */
  437.  
  438. void mh_fetchfast (MAILSTREAM *stream,char *sequence)
  439. {
  440.   return;            /* no-op for local mail */
  441. }
  442.  
  443.  
  444. /* MH mail fetch flags
  445.  * Accepts: MAIL stream
  446.  *        sequence
  447.  */
  448.  
  449. void mh_fetchflags (MAILSTREAM *stream,char *sequence)
  450. {
  451.   return;            /* no-op for local mail */
  452. }
  453.  
  454.  
  455. /* MH mail fetch message structure
  456.  * Accepts: MAIL stream
  457.  *        message # to fetch
  458.  *        pointer to return body
  459.  * Returns: envelope of this message, body returned in body value
  460.  *
  461.  * Fetches the "fast" information as well
  462.  */
  463.  
  464. ENVELOPE *mh_fetchstructure (MAILSTREAM *stream,long msgno,BODY **body)
  465. {
  466.   char *h,*t;
  467.   LONGCACHE *lelt;
  468.   ENVELOPE **env;
  469.   STRING bs;
  470.   BODY **b;
  471.   if (stream->scache) {        /* short cache */
  472.     if (msgno != stream->msgno){/* flush old poop if a different message */
  473.       mail_free_envelope (&stream->env);
  474.       mail_free_body (&stream->body);
  475.     }
  476.     stream->msgno = msgno;
  477.     env = &stream->env;        /* get pointers to envelope and body */
  478.     b = &stream->body;
  479.   }
  480.   else {            /* long cache */
  481.     lelt = mail_lelt (stream,msgno);
  482.     env = &lelt->env;        /* get pointers to envelope and body */
  483.     b = &lelt->body;
  484.   }
  485.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  486.     mail_free_envelope (env);    /* flush old envelope and body */
  487.     mail_free_body (b);
  488.     h = mh_fetchheader (stream,msgno);
  489.                 /* can't use fetchtext since it'll set seen */
  490.     t = LOCAL->body[msgno - 1] ? LOCAL->body[msgno - 1] : "";
  491.     INIT (&bs,mail_string,(void *) t,strlen (t));
  492.                 /* parse envelope and body */
  493.     rfc822_parse_msg (env,body ? b : NIL,h,strlen (h),&bs,lhostn,LOCAL->buf);
  494.   }
  495.   if (body) *body = *b;        /* return the body */
  496.   return *env;            /* return the envelope */
  497. }
  498.  
  499. /* MH mail fetch message header
  500.  * Accepts: MAIL stream
  501.  *        message # to fetch
  502.  * Returns: message header in RFC822 format
  503.  */
  504.  
  505. char *mh_fetchheader (MAILSTREAM *stream,long msgno)
  506. {
  507.   unsigned long i,j;
  508.   int fd;
  509.   char *s,*b,*t;
  510.   long m = msgno - 1;
  511.   long lst = NIL;
  512.   struct stat sbuf;
  513.   struct tm *tm;
  514.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  515.                 /* build message file name */
  516.   sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[m]);
  517.   if (!LOCAL->header[m] && ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
  518.     fstat (fd,&sbuf);        /* get size of message */
  519.                 /* make plausible IMAPish date string */
  520.     tm = gmtime (&sbuf.st_mtime);
  521.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  522.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  523.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  524.     elt->seconds = tm->tm_sec;
  525.     elt->zhours = 0; elt->zminutes = 0;
  526.                 /* slurp message */
  527.     read (fd,s = (char *) fs_get (sbuf.st_size +1),sbuf.st_size);
  528.     s[sbuf.st_size] = '\0';    /* tie off file */
  529.     close (fd);            /* flush message file */
  530.                 /* find end of header and count lines */
  531.     for (i=1,b=s; *b && !(lst && (*b == '\n'));) if (lst = (*b++ == '\n')) i++;
  532.                 /* copy header in CRLF form */
  533.     LOCAL->header[m] = (char *) fs_get (i += (j = b - s));
  534.     elt->rfc822_size = i - 1;    /* size of message header */
  535.     strcrlfcpy (&LOCAL->header[m],&i,s,j);
  536.                 /* copy body in CRLF form */
  537.     for (i = 1,t = b; *t;) if (*t++ == '\n') i++;
  538.     LOCAL->body[m] = (char *) fs_get (i += (j = t - b));
  539.     elt->rfc822_size += i - 1;    /* size of entire message */
  540.     strcrlfcpy (&LOCAL->body[m],&i,b,j);
  541.     fs_give ((void **) &s);    /* flush old data */
  542.   }
  543.   return LOCAL->header[m] ? LOCAL->header[m] : "";
  544. }
  545.  
  546. /* MH mail fetch message text (body only)
  547.  * Accepts: MAIL stream
  548.  *        message # to fetch
  549.  * Returns: message text in RFC822 format
  550.  */
  551.  
  552. char *mh_fetchtext (MAILSTREAM *stream,long msgno)
  553. {
  554.   long i = msgno - 1;
  555.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  556.                 /* snarf message in case don't have it yet */
  557.   mh_fetchheader (stream,msgno);
  558.   if (!elt->seen) {        /* if message not seen before */
  559.     elt->seen = T;        /* mark as seen */
  560.     LOCAL->dirty = T;        /* and that stream is now dirty */
  561.   }
  562.   LOCAL->seen[i] = T;
  563.   return LOCAL->body[i] ? LOCAL->body[i] : "";
  564. }
  565.  
  566. /* MH fetch message body as a structure
  567.  * Accepts: Mail stream
  568.  *        message # to fetch
  569.  *        section specifier
  570.  *        pointer to length
  571.  * Returns: pointer to section of message body
  572.  */
  573.  
  574. char *mh_fetchbody (MAILSTREAM *stream,long m,char *s,unsigned long *len)
  575. {
  576.   BODY *b;
  577.   PART *pt;
  578.   unsigned long i;
  579.   char *base;
  580.   unsigned long offset = 0;
  581.   MESSAGECACHE *elt = mail_elt (stream,m);
  582.                 /* make sure have a body */
  583.   if (!(mh_fetchstructure (stream,m,&b) && b && s && *s &&
  584.     ((i = strtol (s,&s,10)) > 0) && (base = mh_fetchtext (stream,m))))
  585.     return NIL;
  586.   do {                /* until find desired body part */
  587.                 /* multipart content? */
  588.     if (b->type == TYPEMULTIPART) {
  589.       pt = b->contents.part;    /* yes, find desired part */
  590.       while (--i && (pt = pt->next));
  591.       if (!pt) return NIL;    /* bad specifier */
  592.                 /* note new body, check valid nesting */
  593.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  594.       offset = pt->offset;    /* get new offset */
  595.     }
  596.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  597.                 /* need to go down further? */
  598.     if (i = *s) switch (b->type) {
  599.     case TYPEMESSAGE:        /* embedded message */
  600.       offset = b->contents.msg.offset;
  601.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  602.     case TYPEMULTIPART:        /* multipart, get next section */
  603.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  604.     default:            /* bogus subpart specification */
  605.       return NIL;
  606.     }
  607.   } while (i);
  608.                 /* lose if body bogus */
  609.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  610.   if (!elt->seen) {        /* if message not seen before */
  611.     elt->seen = T;        /* mark as seen */
  612.     LOCAL->dirty = T;        /* and that stream is now dirty */
  613.   }
  614.   LOCAL->seen[m-1] = T;
  615.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  616.               b->size.ibytes,b->encoding);
  617. }
  618.  
  619. /* MH mail set flag
  620.  * Accepts: MAIL stream
  621.  *        sequence
  622.  *        flag(s)
  623.  */
  624.  
  625. void mh_setflag (MAILSTREAM *stream,char *sequence,char *flag)
  626. {
  627.   MESSAGECACHE *elt;
  628.   long i;
  629.   short f = mh_getflags (stream,flag);
  630.   short f1 = f & (fSEEN|fDELETED);
  631.   if (!f) return;        /* no-op if no flags to modify */
  632.                 /* get sequence and loop on it */
  633.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  634.     if ((elt = mail_elt (stream,i))->sequence) {
  635.                 /* set all requested flags */
  636.       if (f&fSEEN) elt->seen = T;
  637.       if (f&fDELETED) elt->deleted = T;
  638.       if (f&fFLAGGED) elt->flagged = T;
  639.       if (f&fANSWERED) elt->answered = T;
  640.       if (f1 && !LOCAL->seen[i - 1]) LOCAL->seen[i - 1] = LOCAL->dirty = T;
  641.     }
  642. }
  643.  
  644.  
  645. /* MH mail clear flag
  646.  * Accepts: MAIL stream
  647.  *        sequence
  648.  *        flag(s)
  649.  */
  650.  
  651. void mh_clearflag (MAILSTREAM *stream,char *sequence,char *flag)
  652. {
  653.   MESSAGECACHE *elt;
  654.   long i = stream->nmsgs;
  655.   short f = mh_getflags (stream,flag);
  656.   short f1 = f & (fSEEN|fDELETED);
  657.   if (!f) return;        /* no-op if no flags to modify */
  658.                 /* get sequence and loop on it */
  659.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  660.     if ((elt = mail_elt (stream,i))->sequence) {
  661.                 /* clear all requested flags */
  662.       if (f&fSEEN) elt->seen = NIL;
  663.       if (f&fDELETED) elt->deleted = NIL;
  664.       if (f&fFLAGGED) elt->flagged = NIL;
  665.       if (f&fANSWERED) elt->answered = NIL;
  666.                 /* clearing either seen or deleted does this */
  667.       if (f1 && LOCAL->seen[i - 1]) {
  668.     LOCAL->seen[i - 1] = NIL;
  669.     LOCAL->dirty = T;    /* mark stream as dirty */
  670.       }
  671.     }
  672. }
  673.  
  674. /* MH mail search for messages
  675.  * Accepts: MAIL stream
  676.  *        search criteria
  677.  */
  678.  
  679. void mh_search (MAILSTREAM *stream,char *criteria)
  680. {
  681.   long i,n;
  682.   char *d;
  683.   search_t f;
  684.                 /* initially all searched */
  685.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  686.                 /* get first criterion */
  687.   if (criteria && (criteria = strtok (criteria," "))) {
  688.                 /* for each criterion */
  689.     for (; criteria; (criteria = strtok (NIL," "))) {
  690.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  691.       switch (*ucase (criteria)) {
  692.       case 'A':            /* possible ALL, ANSWERED */
  693.     if (!strcmp (criteria+1,"LL")) f = mh_search_all;
  694.     else if (!strcmp (criteria+1,"NSWERED")) f = mh_search_answered;
  695.     break;
  696.       case 'B':            /* possible BCC, BEFORE, BODY */
  697.     if (!strcmp (criteria+1,"CC"))
  698.       f = mh_search_string (mh_search_bcc,&d,&n);
  699.     else if (!strcmp (criteria+1,"EFORE"))
  700.       f = mh_search_date (mh_search_before,&n);
  701.     else if (!strcmp (criteria+1,"ODY"))
  702.       f = mh_search_string (mh_search_body,&d,&n);
  703.     break;
  704.       case 'C':            /* possible CC */
  705.     if (!strcmp (criteria+1,"C")) 
  706.       f = mh_search_string (mh_search_cc,&d,&n);
  707.     break;
  708.       case 'D':            /* possible DELETED */
  709.     if (!strcmp (criteria+1,"ELETED")) f = mh_search_deleted;
  710.     break;
  711.       case 'F':            /* possible FLAGGED, FROM */
  712.     if (!strcmp (criteria+1,"LAGGED")) f = mh_search_flagged;
  713.     else if (!strcmp (criteria+1,"ROM"))
  714.       f = mh_search_string (mh_search_from,&d,&n);
  715.     break;
  716.       case 'K':            /* possible KEYWORD */
  717.     if (!strcmp (criteria+1,"EYWORD"))
  718.       f = mh_search_flag (mh_search_keyword,&d);
  719.     break;
  720.       case 'N':            /* possible NEW */
  721.     if (!strcmp (criteria+1,"EW")) f = mh_search_new;
  722.     break;
  723.  
  724.       case 'O':            /* possible OLD, ON */
  725.     if (!strcmp (criteria+1,"LD")) f = mh_search_old;
  726.     else if (!strcmp (criteria+1,"N"))
  727.       f = mh_search_date (mh_search_on,&n);
  728.     break;
  729.       case 'R':            /* possible RECENT */
  730.     if (!strcmp (criteria+1,"ECENT")) f = mh_search_recent;
  731.     break;
  732.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  733.     if (!strcmp (criteria+1,"EEN")) f = mh_search_seen;
  734.     else if (!strcmp (criteria+1,"INCE"))
  735.       f = mh_search_date (mh_search_since,&n);
  736.     else if (!strcmp (criteria+1,"UBJECT"))
  737.       f = mh_search_string (mh_search_subject,&d,&n);
  738.     break;
  739.       case 'T':            /* possible TEXT, TO */
  740.     if (!strcmp (criteria+1,"EXT"))
  741.       f = mh_search_string (mh_search_text,&d,&n);
  742.     else if (!strcmp (criteria+1,"O"))
  743.       f = mh_search_string (mh_search_to,&d,&n);
  744.     break;
  745.       case 'U':            /* possible UN* */
  746.     if (criteria[1] == 'N') {
  747.       if (!strcmp (criteria+2,"ANSWERED")) f = mh_search_unanswered;
  748.       else if (!strcmp (criteria+2,"DELETED")) f = mh_search_undeleted;
  749.       else if (!strcmp (criteria+2,"FLAGGED")) f = mh_search_unflagged;
  750.       else if (!strcmp (criteria+2,"KEYWORD"))
  751.         f = mh_search_flag (mh_search_unkeyword,&d);
  752.       else if (!strcmp (criteria+2,"SEEN")) f = mh_search_unseen;
  753.     }
  754.     break;
  755.       default:            /* we will barf below */
  756.     break;
  757.       }
  758.       if (!f) {            /* if can't determine any criteria */
  759.     sprintf (LOCAL->buf,"Unknown search criterion: %s",criteria);
  760.     mm_log (LOCAL->buf,ERROR);
  761.     return;
  762.       }
  763.                 /* run the search criterion */
  764.       for (i = 1; i <= stream->nmsgs; ++i)
  765.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  766.       mail_elt (stream,i)->searched = NIL;
  767.     }
  768.                 /* report search results to main program */
  769.     for (i = 1; i <= stream->nmsgs; ++i)
  770.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  771.   }
  772. }
  773.  
  774. /* MH mail ping mailbox
  775.  * Accepts: MAIL stream
  776.  * Returns: T if stream alive, else NIL
  777.  */
  778.  
  779. long mh_ping (MAILSTREAM *stream)
  780. {
  781.   return T;            /* always alive */
  782. }
  783.  
  784.  
  785. /* MH mail check mailbox
  786.  * Accepts: MAIL stream
  787.  */
  788.  
  789. void mh_check (MAILSTREAM *stream)
  790. {
  791.  /* A no-op for starters */
  792. }
  793.  
  794.  
  795. /* MH mail expunge mailbox
  796.  * Accepts: MAIL stream
  797.  */
  798.  
  799. void mh_expunge (MAILSTREAM *stream)
  800. {
  801.   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  802. }
  803.  
  804. /* MH mail copy message(s)
  805.  * Accepts: MAIL stream
  806.  *        sequence
  807.  *        destination mailbox
  808.  * Returns: T if copy successful, else NIL
  809.  */
  810.  
  811. long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox)
  812. {
  813.   char tmp[MAILTMPLEN];
  814.   char lock[MAILTMPLEN];
  815.   struct iovec iov[3];
  816.   struct stat ssbuf,dsbuf;
  817.   char *t,*v;
  818.   int sfd,dfd;
  819.   long i;
  820.   long r = NIL;
  821.                 /* get sequence to do */
  822.   if (!mail_sequence (stream,sequence)) return NIL;
  823.                 /* get destination mailbox */
  824.   if ((dfd = bezerk_lock (bezerk_file (tmp,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  825.               S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  826.     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  827.     mm_log (LOCAL->buf,ERROR);
  828.     return NIL;
  829.   }
  830.   mm_critical (stream);        /* go critical */
  831.   fstat (dfd,&dsbuf);        /* get current file size */
  832.   iov[2].iov_base = "\n\n";    /* constant trailer */
  833.   iov[2].iov_len = 2;
  834.  
  835.                 /* write all requested messages to mailbox */
  836.   for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
  837.                 /* build message file name */
  838.     sprintf (tmp,"%s/%lu",LOCAL->dir,LOCAL->number[i - 1]);
  839.     if ((sfd = open (tmp,O_RDONLY,NIL)) >= 0) {
  840.       fstat (sfd,&ssbuf);    /* get size of message */
  841.                 /* ensure enough room */
  842.       if (ssbuf.st_size > LOCAL->buflen) {
  843.                 /* fs_resize does an unnecessary copy */
  844.     fs_give ((void **) &LOCAL->buf);
  845.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = ssbuf.st_size) + 1);
  846.       }
  847.                 /* slurp the silly thing in */
  848.       read (sfd,iov[1].iov_base = LOCAL->buf,iov[1].iov_len = ssbuf.st_size);
  849.                 /* tie off file */
  850.       iov[1].iov_base[ssbuf.st_size] = '\0';
  851.       close (sfd);        /* flush message file */
  852.                 /* get Path: data */
  853.       if ((((t = iov[1].iov_base - 1) && t[1] == 'P' && t[2] == 'a' &&
  854.         t[3] == 't' && t[4] == 'h' && t[5] == ':' && t[6] == ' ') ||
  855.        (t = strstr (iov[1].iov_base,"\nPath: "))) &&
  856.       (v = strchr (t += 7,'\n')) && (r = v - t)) {
  857.     strcpy (tmp,"From ");    /* start text */
  858.     strncpy (v = tmp+5,t,r);/* copy that many characters */
  859.     v[r++] = ' ';        /* delimiter */
  860.     v[r] = '\0';        /* tie it off */
  861.       }
  862.       else strcpy (tmp,"From somebody ");
  863.                 /* add the time and a newline */
  864.       strcat (tmp,ctime (&ssbuf.st_mtime));
  865.       iov[0].iov_len = strlen (iov[0].iov_base = tmp);
  866.                 /* now do the write */
  867.       if (r = (writev (dfd,iov,3) < 0)) {
  868.     sprintf (LOCAL->buf,"Message copy %d failed: %s",i,strerror (errno));
  869.     mm_log (LOCAL->buf,ERROR);
  870.     ftruncate (dfd,dsbuf.st_size);
  871.     break;            /* give up */
  872.       }
  873.     }
  874.   }
  875.   fsync (dfd);            /* force out the update */
  876.   bezerk_unlock (dfd,NIL,lock);    /* unlock and close mailbox */
  877.   mm_nocritical (stream);    /* release critical */
  878.   return !r;            /* return whether or not succeeded */
  879. }
  880.  
  881. /* MH mail move message(s)
  882.  * Accepts: MAIL stream
  883.  *        sequence
  884.  *        destination mailbox
  885.  * Returns: T if move successful, else NIL
  886.  */
  887.  
  888. long mh_move (MAILSTREAM *stream,char *sequence,char *mailbox)
  889. {
  890.   long i;
  891.   MESSAGECACHE *elt;
  892.   if (!(mail_sequence (stream,sequence) &&
  893.     mh_copy (stream,sequence,mailbox))) return NIL;
  894.                 /* delete all requested messages */
  895.   for (i = 1; i <= stream->nmsgs; i++)
  896.     if ((elt = mail_elt (stream,i))->sequence) {
  897.       elt->deleted = T;        /* mark message deleted */
  898.       LOCAL->dirty = T;        /* mark mailbox as dirty */
  899.       LOCAL->seen[i - 1] = T;    /* and seen for .mhrc update */
  900.     }
  901.   return T;
  902. }
  903.  
  904.  
  905. /* MH mail append message from stringstruct
  906.  * Accepts: MAIL stream
  907.  *        destination mailbox
  908.  *        stringstruct of messages to append
  909.  * Returns: T if append successful, else NIL
  910.  */
  911.  
  912. long mh_append (MAILSTREAM *stream,char *mailbox,STRING *message)
  913. {
  914.   mm_log ("Append not valid for read-only mh mailbox",ERROR);
  915.   return NIL;
  916. }
  917.  
  918.  
  919. /* MH garbage collect stream
  920.  * Accepts: Mail stream
  921.  *        garbage collection flags
  922.  */
  923.  
  924. void mh_gc (MAILSTREAM *stream,long gcflags)
  925. {
  926.   unsigned long i;
  927.   if (gcflags & GC_TEXTS)    /* garbage collect texts? */
  928.                 /* flush texts from cache */
  929.     for (i = 0; i < stream->nmsgs; i++) {
  930.       if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  931.       if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  932.     }
  933. }
  934.  
  935. /* Internal routines */
  936.  
  937.  
  938. /* Parse flag list
  939.  * Accepts: MAIL stream
  940.  *        flag list as a character string
  941.  * Returns: flag command list
  942.  */
  943.  
  944. short mh_getflags (MAILSTREAM *stream,char *flag)
  945. {
  946.   char *t;
  947.   short f = 0;
  948.   short i,j;
  949.   if (flag && *flag) {        /* no-op if no flag string */
  950.                 /* check if a list and make sure valid */
  951.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  952.       mm_log ("Bad flag list",ERROR);
  953.       return NIL;
  954.     }
  955.                 /* copy the flag string w/o list construct */
  956.     strncpy (LOCAL->buf,flag+i,(j = strlen (flag) - (2*i)));
  957.     LOCAL->buf[j] = '\0';
  958.     t = ucase (LOCAL->buf);    /* uppercase only from now on */
  959.  
  960.     while (*t) {        /* parse the flags */
  961.       if (*t == '\\') {        /* system flag? */
  962.     switch (*++t) {        /* dispatch based on first character */
  963.     case 'S':        /* possible \Seen flag */
  964.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  965.       t += 4;        /* skip past flag name */
  966.       break;
  967.     case 'D':        /* possible \Deleted flag */
  968.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  969.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  970.       t += 7;        /* skip past flag name */
  971.       break;
  972.     case 'F':        /* possible \Flagged flag */
  973.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  974.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  975.       t += 7;        /* skip past flag name */
  976.       break;
  977.     case 'A':        /* possible \Answered flag */
  978.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  979.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  980.       t += 8;        /* skip past flag name */
  981.       break;
  982.     default:        /* unknown */
  983.       i = 0;
  984.       break;
  985.     }
  986.                 /* add flag to flags list */
  987.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  988.     else {            /* bitch about bogus flag */
  989.       mm_log ("Unknown system flag",ERROR);
  990.       return NIL;
  991.     }
  992.       }
  993.       else {            /* no user flags yet */
  994.     mm_log ("Unknown flag",ERROR);
  995.     return NIL;
  996.       }
  997.     }
  998.   }
  999.   return f;
  1000. }
  1001.  
  1002. /* Search support routines
  1003.  * Accepts: MAIL stream
  1004.  *        message number
  1005.  *        pointer to additional data
  1006.  *        pointer to temporary buffer
  1007.  * Returns: T if search matches, else NIL
  1008.  */
  1009.  
  1010. char mh_search_all (MAILSTREAM *stream,long msgno,char *d,long n)
  1011. {
  1012.   return T;            /* ALL always succeeds */
  1013. }
  1014.  
  1015.  
  1016. char mh_search_answered (MAILSTREAM *stream,long msgno,char *d,long n)
  1017. {
  1018.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1019. }
  1020.  
  1021.  
  1022. char mh_search_deleted (MAILSTREAM *stream,long msgno,char *d,long n)
  1023. {
  1024.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1025. }
  1026.  
  1027.  
  1028. char mh_search_flagged (MAILSTREAM *stream,long msgno,char *d,long n)
  1029. {
  1030.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1031. }
  1032.  
  1033.  
  1034. char mh_search_keyword (MAILSTREAM *stream,long msgno,char *d,long n)
  1035. {
  1036.   return NIL;            /* keywords not supported yet */
  1037. }
  1038.  
  1039.  
  1040. char mh_search_new (MAILSTREAM *stream,long msgno,char *d,long n)
  1041. {
  1042.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1043.   return (elt->recent && !elt->seen) ? T : NIL;
  1044. }
  1045.  
  1046. char mh_search_old (MAILSTREAM *stream,long msgno,char *d,long n)
  1047. {
  1048.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1049. }
  1050.  
  1051.  
  1052. char mh_search_recent (MAILSTREAM *stream,long msgno,char *d,long n)
  1053. {
  1054.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1055. }
  1056.  
  1057.  
  1058. char mh_search_seen (MAILSTREAM *stream,long msgno,char *d,long n)
  1059. {
  1060.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1061. }
  1062.  
  1063.  
  1064. char mh_search_unanswered (MAILSTREAM *stream,long msgno,char *d,long n)
  1065. {
  1066.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1067. }
  1068.  
  1069.  
  1070. char mh_search_undeleted (MAILSTREAM *stream,long msgno,char *d,long n)
  1071. {
  1072.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1073. }
  1074.  
  1075.  
  1076. char mh_search_unflagged (MAILSTREAM *stream,long msgno,char *d,long n)
  1077. {
  1078.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1079. }
  1080.  
  1081.  
  1082. char mh_search_unkeyword (MAILSTREAM *stream,long msgno,char *d,long n)
  1083. {
  1084.   return T;            /* keywords not supported yet */
  1085. }
  1086.  
  1087.  
  1088. char mh_search_unseen (MAILSTREAM *stream,long msgno,char *d,long n)
  1089. {
  1090.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1091. }
  1092.  
  1093. char mh_search_before (MAILSTREAM *stream,long msgno,char *d,long n)
  1094. {
  1095.   return (char) (mh_msgdate (stream,msgno) < n);
  1096. }
  1097.  
  1098.  
  1099. char mh_search_on (MAILSTREAM *stream,long msgno,char *d,long n)
  1100. {
  1101.   return (char) (mh_msgdate (stream,msgno) == n);
  1102. }
  1103.  
  1104.  
  1105. char mh_search_since (MAILSTREAM *stream,long msgno,char *d,long n)
  1106. {
  1107.                 /* everybody interprets "since" as .GE. */
  1108.   return (char) (mh_msgdate (stream,msgno) >= n);
  1109. }
  1110.  
  1111.  
  1112. unsigned long mh_msgdate (MAILSTREAM *stream,long msgno)
  1113. {
  1114.   struct stat sbuf;
  1115.   struct tm *tm;
  1116.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1117.   if (!elt->day) {        /* get date if don't have it yet */
  1118.                 /* build message file name */
  1119.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[msgno - 1]);
  1120.     stat (LOCAL->buf,&sbuf);    /* get message date */
  1121.     tm = gmtime (&sbuf.st_mtime);
  1122.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  1123.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  1124.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  1125.     elt->seconds = tm->tm_sec;
  1126.     elt->zhours = 0; elt->zminutes = 0;
  1127.   }
  1128.   return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
  1129. }
  1130.  
  1131. char mh_search_body (MAILSTREAM *stream,long msgno,char *d,long n)
  1132. {
  1133.   long i = msgno - 1;
  1134.   mh_fetchheader (stream,msgno);
  1135.   return LOCAL->body[i] ?
  1136.     search (LOCAL->body[i],strlen (LOCAL->body[i]),d,n) : NIL;
  1137. }
  1138.  
  1139.  
  1140. char mh_search_subject (MAILSTREAM *stream,long msgno,char *d,long n)
  1141. {
  1142.   char *t = mh_fetchstructure (stream,msgno,NIL)->subject;
  1143.   return t ? search (t,strlen (t),d,n) : NIL;
  1144. }
  1145.  
  1146.  
  1147. char mh_search_text (MAILSTREAM *stream,long msgno,char *d,long n)
  1148. {
  1149.   char *t = mh_fetchheader (stream,msgno);
  1150.   return (t && search (t,strlen (t),d,n)) ||
  1151.     mh_search_body (stream,msgno,d,n);
  1152. }
  1153.  
  1154. char mh_search_bcc (MAILSTREAM *stream,long msgno,char *d,long n)
  1155. {
  1156.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1157.                 /* get text for address */
  1158.   rfc822_write_address (LOCAL->buf,mh_fetchstructure (stream,msgno,NIL)->bcc);
  1159.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1160. }
  1161.  
  1162.  
  1163. char mh_search_cc (MAILSTREAM *stream,long msgno,char *d,long n)
  1164. {
  1165.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1166.                 /* get text for address */
  1167.   rfc822_write_address (LOCAL->buf,mh_fetchstructure (stream,msgno,NIL)->cc);
  1168.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1169. }
  1170.  
  1171.  
  1172. char mh_search_from (MAILSTREAM *stream,long msgno,char *d,long n)
  1173. {
  1174.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1175.                 /* get text for address */
  1176.   rfc822_write_address (LOCAL->buf,mh_fetchstructure (stream,msgno,NIL)->from);
  1177.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1178. }
  1179.  
  1180.  
  1181. char mh_search_to (MAILSTREAM *stream,long msgno,char *d,long n)
  1182. {
  1183.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1184.                 /* get text for address */
  1185.   rfc822_write_address (LOCAL->buf,mh_fetchstructure (stream,msgno,NIL)->to);
  1186.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1187. }
  1188.  
  1189. /* Search parsers */
  1190.  
  1191.  
  1192. /* Parse a date
  1193.  * Accepts: function to return
  1194.  *        pointer to date integer to return
  1195.  * Returns: function to return
  1196.  */
  1197.  
  1198. search_t mh_search_date (search_t f,long *n)
  1199. {
  1200.   long i;
  1201.   char *s;
  1202.   MESSAGECACHE elt;
  1203.                 /* parse the date and return fn if OK */
  1204.   return (mh_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  1205.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  1206. }
  1207.  
  1208. /* Parse a flag
  1209.  * Accepts: function to return
  1210.  *        pointer to string to return
  1211.  * Returns: function to return
  1212.  */
  1213.  
  1214. search_t mh_search_flag (search_t f,char **d)
  1215. {
  1216.                 /* get a keyword, return if OK */
  1217.   return (*d = strtok (NIL," ")) ? f : NIL;
  1218. }
  1219.  
  1220.  
  1221. /* Parse a string
  1222.  * Accepts: function to return
  1223.  *        pointer to string to return
  1224.  *        pointer to string length to return
  1225.  * Returns: function to return
  1226.  */
  1227.  
  1228. search_t mh_search_string (search_t f,char **d,long *n)
  1229. {
  1230.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1231.   if (c) {            /* better be an argument */
  1232.     switch (*c) {        /* see what the argument is */
  1233.     case '\0':            /* catch bogons */
  1234.     case ' ':
  1235.       return NIL;
  1236.     case '"':            /* quoted string */
  1237.       if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
  1238.     return NIL;
  1239.       break;
  1240.     case '{':            /* literal string */
  1241.       *n = strtol (c+1,&c,10);    /* get its length */
  1242.       if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
  1243.       *n > strlen (*d = c)) return NIL;
  1244.       c[*n] = '\255';        /* write new delimiter */
  1245.       strtok (c,"\255");    /* reset the strtok mechanism */
  1246.       break;
  1247.     default:            /* atomic string */
  1248.       *n = strlen (*d = strtok (c," "));
  1249.       break;
  1250.     }
  1251.     return f;
  1252.   }
  1253.   else return NIL;
  1254. }
  1255.